using Caliburn.Micro;
using SS.WPF.Framework.Restriction;
using SS.WPF.Framework.ViewModel.Screen;

namespace SS.WPF.Toolkit.PropertyEditor
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Reflection;

    /// <summary>
    /// The class implement interface ICustomTypeDescriptor to supply dynamic custom type information
    /// </summary>
    /// <typeparam name="T">Custom type information</typeparam>
    public abstract class CustomTypeDescriptorBase<T> : EntityViewModel<T>, ICustomTypeDescriptor
    {
        /// <summary>
        /// Create object with specified data input
        /// </summary>
        /// <param name="data">Data input</param>
        protected CustomTypeDescriptorBase(T data)
            : base(data)
        {
        }

        private PropertyDescriptorCollection _propertyDescriptorCollectionCache;

        #region Implementation of ICustomTypeDescriptor

        /// <summary>
        /// Returns a collection of custom attributes for this instance of a component.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.ComponentModel.AttributeCollection"/> containing the attributes for this object.
        /// </returns>
        public AttributeCollection GetAttributes()
        {
            return new AttributeCollection(null);
        }

        /// <summary>
        /// Returns the class name of this instance of a component.
        /// </summary>
        /// <returns>
        /// The class name of the object, or null if the class does not have a name.
        /// </returns>
        public string GetClassName()
        {
            return typeof(T).Name;
        }

        /// <summary>
        /// Returns the name of this instance of a component.
        /// </summary>
        /// <returns>
        /// The name of the object, or null if the object does not have a name.
        /// </returns>
        public string GetComponentName()
        {
            return typeof(T).Name;
        }

        /// <summary>
        /// Returns a type converter for this instance of a component.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.TypeConverter"/> that is the converter for this object, or null if there is no <see cref="T:System.ComponentModel.TypeConverter"/> for this object.
        /// </returns>
        public TypeConverter GetConverter()
        {
            return new TypeConverter();
        }

        /// <summary>
        /// Returns the default event for this instance of a component.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.ComponentModel.EventDescriptor"/> that represents the default event for this object, or null if this object does not have events.
        /// </returns>
        public EventDescriptor GetDefaultEvent()
        {
            return null;
        }

        /// <summary>
        /// Returns the default property for this instance of a component.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.PropertyDescriptor"/> that represents the default property for this object, or null if this object does not have properties.
        /// </returns>
        public PropertyDescriptor GetDefaultProperty()
        {
            return null;
        }

        /// <summary>
        /// Returns an editor of the specified type for this instance of a component.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Object"/> of the specified type that is the editor for this object, or null if the editor cannot be found.
        /// </returns>
        /// <param name="editorBaseType">A <see cref="T:System.Type"/> that represents the editor for this object. </param>
        public object GetEditor(Type editorBaseType)
        {
            return null;
        }

        /// <summary>
        /// Returns the events for this instance of a component.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.ComponentModel.EventDescriptorCollection"/> that represents the events for this component instance.
        /// </returns>
        public EventDescriptorCollection GetEvents()
        {
            return new EventDescriptorCollection(null);
        }

        /// <summary>
        /// Returns the events for this instance of a component using the specified attribute array as a filter.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.ComponentModel.EventDescriptorCollection"/> that represents the filtered events for this component instance.
        /// </returns>
        /// <param name="attributes">An array of type <see cref="T:System.Attribute"/> that is used as a filter. </param>
        public EventDescriptorCollection GetEvents(Attribute[] attributes)
        {
            return new EventDescriptorCollection(null);
        }

        /// <summary>
        /// Returns the properties for this instance of a component.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.PropertyDescriptorCollection"/> that represents the properties for this component instance.
        /// </returns>
        public PropertyDescriptorCollection GetProperties()
        {
            if (_propertyDescriptorCollectionCache == null)
            {
                var propertyInfos = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

                var list = new List<PropertyDescriptor>();
                foreach (var propertyInfo in propertyInfos)
                {
                    var customAttributes = propertyInfo.GetCustomAttributes(typeof(DynamicPropertyAttribute), true);
                    if (customAttributes.Length > 0)
                    {
                        Type propertyType = null;
                        IRestriction restriction;
                        string validationGroup = null;

                        if (!propertyInfo.PropertyType.IsEnum)
                        {
                            var attr = (DynamicPropertyAttribute)customAttributes[0];
                            validationGroup = attr.ValidationGroup;

                            if (IoC.Get<IRestrictionDictionary>().TryGetValue(propertyInfo.Name, out restriction))
                            {
                                var enumRestriction = restriction as IEnumRestriction;
                                if (enumRestriction != null)
                                {
                                    propertyType = enumRestriction.EnumType;
                                }
                            }
                        }
                        else
                        {
                            propertyType = propertyInfo.PropertyType;
                            restriction = new StringEnumRestriction
                                              {
                                                  EnumType = propertyType
                                              };
                        }

                        var displayName = propertyInfo.Name;

                        var classPropertyDescriptor = new CustomPropertyDescriptor<T>(displayName, propertyInfo.Name,
                                                                                      propertyInfo, propertyType,
                                                                                      restriction)
                                                          {
                                                              ValidationGroup = validationGroup
                                                          };
                        list.Add(classPropertyDescriptor);
                    }
                }

                var properties = list.ToArray();

                _propertyDescriptorCollectionCache = new PropertyDescriptorCollection(properties);
            }

            return _propertyDescriptorCollectionCache;
        }

        /// <summary>
        /// Returns the properties for this instance of a component using the attribute array as a filter.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.PropertyDescriptorCollection"/> that represents the filtered properties for this component instance.
        /// </returns>
        /// <param name="attributes">An array of type <see cref="T:System.Attribute"/> that is used as a filter. </param>
        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            return ((ICustomTypeDescriptor)this).GetProperties(null);
        }

        /// <summary>
        /// Returns an object that contains the property described by the specified property descriptor.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Object"/> that represents the owner of the specified property.
        /// </returns>
        /// <param name="pd">A <see cref="T:System.ComponentModel.PropertyDescriptor"/> that represents the property whose owner is to be found. </param>
        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }

        #endregion
    }
}